# -*- coding: utf-8 -*-
import logging
import random
import string
import time
import uuid

from flask import request, abort

from . import api
from .. import utils

LOGGER = logging.getLogger(__name__)


@api.route('/test')
def api_test():
    LOGGER.debug('api_test')
    return {'api_version': 2}


# Instance.health
@api.route('/instances/<instance_id>/health')
def get_instance_health_data(instance_id):
    args = request.args

    metric = args['metric']
    stime = float(args['stime'])
    etime = float(args['etime'])

    time_fmt = '%Y/%m/%d-%H:%M:%S'
    stime = time.strftime(time_fmt, time.localtime(stime))
    etime = time.strftime(time_fmt, time.localtime(etime))

    s, m = utils.get_instance_health_data(instance_id, metric,
                                          stime, etime)
    resp = {
        'status': s,
        'msg': 'fail' if s else 'success',
        'data': m,
    }
    return resp


# Instance.OPLog
@api.route('/instances/<instance_id>/oplog')
def get_instance_oplog(instance_id):
    args = request.args

    try:
        page_size = int(args.get('page_size', 10))
        page_num = int(args.get('page_num', 0))

        offset = page_size * page_num

        s, m = utils.get_instance_oplog(instance_id, page_size, offset)
        resp = {
            'status': s,
            'msg': 'fail' if s else 'success',
            'data': m,
        }
        return resp
    except ValueError, ve:
        LOGGER.exception(ve.message)
        return dict(status=4, msg='fail', data=str(ve))


# Instance.VNC
@api.route('/instances/<instance_id>/vnc')
def get_instance_vnc(instance_id):
    status, data = utils.get_instance_info(instance_id)
    if status:
        LOGGER.error('Failed to find instance %s: %s!', instance_id, data)
        return dict(status=status, msg='fail', data=data)

    cluster_id = data['cluster_id']
    project_id = data['project_id']
    user_name = request.headers['user_name']
    if not utils.is_project_admin(user_name, project_id, True):
        if not utils.is_active(project_id, user_name):
            return dict(status=403, msg='fail', data=u'您已被冻结!')
        if not utils.check_permission(user_name, project_id, 'vps', 'u'):
            return dict(status=403, msg='fail', data=u'您无权进入虚拟机!')

    s, m = utils.get_vnc(cluster_id, instance_id)
    resp = {
        'status': s,
        'msg': 'fail' if s else 'success',
        'data': m,
    }
    return dict(resp)


# Instances Interface
@api.route('/instances/<instance_id>/interfaces', methods=['POST'])
def attache_interface(instance_id):
    args = request.form

    # project_id = args['project_id']
    net_id = args['net_id']
    fixed_ip = args.get('fixed_ip')

    s, m = utils.attach_interface(instance_id, net_id, fixed_ip)
    resp = {
        'status': s,
        'msg': 'fail' if s else 'success',
        'data': m,
    }
    return dict(resp)


@api.route('/instances/<instance_id>/interfaces/<port_id>', methods=['DELETE'])
def detach_interface(instance_id, port_id):
    # project_id = args['project_id']

    s, m = utils.dettach_interface(instance_id, port_id)
    resp = {
        'status': s,
        'msg': 'fail' if s else 'success',
        'data': m,
    }
    return dict(resp)


@api.route('/instances/<instance_id>/interfaces')
def show_instance_interface(instance_id):
    # args = request.form

    # project_id = args['project_id']

    s, m = utils.list_instance_interface(instance_id)
    resp = {
        'status': s,
        'msg': 'fail' if s else 'success',
        'data': m,
    }
    return dict(resp)


class Instance(object):

    os = None

    def __init__(self, name, image=None, flavor=None, hostname=None):
        self.name = name
        self.image = image
        self.flavor = flavor
        self.hostname = hostname

    @classmethod
    def is_linux(cls):
        return cls.os == 'linux'

    @classmethod
    def is_windows(cls):
        return cls.os == 'windows'

    def __repr__(self):
        return '<%s "%s">' % (self.__class__.__name__, self.name)


class CreateMixin(object):

    def params_for_spawn(self):
        raise NotImplementedError()

    def spawn(self, cluster_id, private_net,
              user=None, public_net=False, count=1):
        values = self.params_for_spawn()

        # Public network
        if public_net:
            values.update(public_net=public_net)

        # Append common parameters
        create_time = time.strftime('%Y-%m-%d %H:%M:%S')
        values.update(user=user or 'anonymous',
                      cluster_id=cluster_id,
                      private_net=private_net,
                      count=count,
                      create_time=create_time)
        print 'create values: %s' % values
        rv = utils.create_instance(values, version=2)
        print 'rv: ', rv
        if rv[0]:
            return rv
        else:
            rv[1].update(values)
        return rv


class LinuxInstance(Instance, CreateMixin):
    """ Linux instance.

        >>> linux = LinuxInstance('test-server')
        <LinuxInstance "test-server">
        >>> linux.is_linux
        True
        >>> linux.is_windows()
        False
        # >>> linux.spawn(user='yj', cluster_id=0, need_pub=False)

    """
    os = 'linux'
    router_path = '/etc/sysconfig/network-scripts/route-eth0'

    def __init__(self, *a, **kw):
        self.key_name = kw.pop('key_name', None)
        super(LinuxInstance, self).__init__(*a, **kw)

    def params_for_spawn(self):
        values = {
            'name': self.name,
            'image_ref': self.image,
            'flavor_ref': self.flavor,
            'hostname': self.hostname,
            'key_name': self.key_name,
            'router_path': self.router_path,
        }
        return values


class WindowsInstance(Instance, CreateMixin):
    """ Windows Instance.

        >>> win = WindowsInstance("win-server1")
        <WindowsInstance "win-server1">
        >>> win.is_linux()
        False
        >>> win.is_windows()
        True
    """
    os = 'windows'

    def params_for_spawn(self):
        # TODO
        return {}


INSTANCE_CLS = {
    'linux': LinuxInstance,
    'windows': WindowsInstance,
}


@api.route('/instances', methods=['POST'])
def spawn_instance_v2():
    global INSTANCE_CLS

    args = request.form
    LOGGER.debug('args: %s', args)

    # Params
    project_id = args['project_id']
    cluster_id = args['cluster_id']
    image_id = args['img_id']
    image_uuid = args['img_uuid']
    os = args.get('os', 'linux')
    flavor_id = args['flavor_id']
    flavor_uuid = args['flavor_uuid']
    count = args.get('count', 1)

    # flavor_name = args['flavor_name']
    need_pub = int(args.get('need_pub', 0))
    private_net = args['network_id']
    private_netname = args['network_name']
    hostname = args.get('hostname', None)

    # Check permission
    user_name = request.headers['user_name']
    if not utils.is_project_admin(user_name, project_id, True):
        if not utils.is_active(project_id, user_name):
            return dict(status=403, msg='fail', data=u'您已被冻结!')

        # Check action permission
        if not utils.check_permission(user_name, project_id, 'vps', 'c'):
            return dict(status=403, msg='fail', data=u'您无权创建虚拟机!')

    # Instance creator class
    instance_cls = INSTANCE_CLS.get(os, None)
    if instance_cls is None:
        abort(400)

    mid_names = (string.digits, string.ascii_lowercase, string.ascii_uppercase)
    mid_name = ''.join(map(lambda s: random.choice(s), mid_names))
    name = '_'.join([os, mid_name, uuid.uuid4().hex[:7]])
    if instance_cls.is_linux:
        key_name = args['key_name']
        instance = LinuxInstance(name, image_uuid, flavor_uuid,
                                 hostname, key_name=key_name)
    else:
        instance = WindowsInstance(name, image_uuid, flavor_uuid,
                                   hostname)

    # Choose a public network
    if need_pub:
        public_network = utils.pick_public_network()
    else:
        public_network = {}

    public_net = public_network.get('network_id')
    public_netname = public_network.get('name')

    err, result = instance.spawn(cluster_id, private_net, user_name,
                                 public_net, count)
    if not err:
        if not result['status']:
            result_body = result['result']

            for _instance in result_body['instances']:
                instance.id = _instance['instance_id']
                instance.name = _instance['vm_name']
                cfg = {
                    'cluster_id': cluster_id,
                    'image_id': image_id,
                    'flavor_id': flavor_id,
                    'create_time': result['create_time'],
                    'project_id': project_id,
                    'user': user_name,
                    'status': _instance['status'],
                    'private_ip': _instance['addresses'].get(private_netname, ''),
                }
                if need_pub:
                    cfg['public_ip'] = _instance['addresses'].get(public_netname, '')

                # save_record_v2(instance, cluster_id, image_id,
                #                flavor_id, create_time, project_id,
                #                user)
                utils.save_record_v2(instance, **cfg)
                LOGGER.debug('private_netname: %s', private_netname)
                LOGGER.debug('public_netname: %s', public_netname)

                # TODO: send sync request
                s, m = utils.sync_network_info(instance.id, cluster_id,
                                               private_netname, public_netname)
                if s:
                    LOGGER.error('sync network: %s', m)
                else:
                    LOGGER.debug('sync network: %s', m)
            return {'status': 0, 'msg': 'ok', 'data': 'create ok!'}
        else:
            return {'status': 2, 'msg': 'fail', 'data': result['result']}
    else:
        return {'status': 1, 'msg': 'fail', 'data': '%s,%s' % (err, result)}


# Instance.Tag
@api.route('/instances/<instance_id>', methods=['PATCH'])
def patch_instance(instance_id):
    s, instance_info = utils.get_instance_info(instance_id)
    if s or not instance_info:
        return dict(status=404, msg='fail', data=u'无法查询到此虚拟机!')

    project_id = instance_info['project_id']
    x_user = request.headers['user_name']
    if not utils.is_project_admin(x_user, project_id, True):
        if not utils.is_active(project_id, x_user):
            return dict(status=403, msg='fail', data=u'您已被冻结!')
        if not utils.check_permission(x_user, project_id, 'vps', 'u'):
            return dict(status=403, msg='fail', data=u'您无权修改虚拟机备注!')

    args = request.form
    tag = args['tag']

    s, ret = utils.patch_instance(instance_id,
                                  x_user,
                                  tag)
    resp = {
        'status': s,
        'msg': 'fail' if s else 'success',
        'data': ret,
    }
    return resp


@api.route('/projects/<project_id>/keys_gen', methods=['POST'])
def gen_project_keypairs(project_id):
    user_name = request.headers['user_name']
    if not utils.is_project_admin(user_name, project_id, only_creator=True):
        if not utils.is_active(project_id, user_name):
            return dict(status=403, msg='fail', data=u'您已被冻结!')
        if not utils.check_permission(user_name, project_id, 'vps', 'c'):
            return dict(status=403, msg='fail', data=u'您无权创建密钥对!')

    args = request.form
    show_name = args.get('show_name')
    key_id = '_'.join(['sk', uuid.uuid4().hex[:4], user_name])

    cluster_ids = utils.get_project_clusters_ids(project_id, True)

    if cluster_ids:
        # auto create
        first_cid = cluster_ids.pop(0)

        print 'project_id = %s' % project_id
        print 'user_id = %s' % user_name
        print 'key_id = %s' % key_id

        ok, new_key = utils.create_single_keypair(project_id, first_cid,
                                                  user_name, key_id, show_name)

        if not ok:
            return {'status': 2, 'msg': 'fail', 'data': new_key}

        # post keypairs
        for cid in cluster_ids:
            values = {
                'project_id': project_id,
                'cluster_id': cid,
                'user_id': user_name,
                'key_id': key_id,
                'key_content': new_key['public_key'],
                'show_name': show_name,
            }
            utils.post_single_keypairs(**values)

        # resp = make_response(new_key['private_key'])
        # headers = {
        #     'Content-Type': 'application/octet-stream',
        #     'Content-Disposition': 'attachment; filename=%s.pem' % key_id,
        # }
        # resp.headers.extend(headers)
        resp = {
            'status': 0,
            'msg': 'success',
            'data': {
                'private_key': new_key['private_key'],
                'key_name': show_name,
            },
        }
        return resp, 201
    else:
        return {'status': 1, 'msg': 'fail', 'data': 'cluster not found'}


@api.route('/projects/<project_id>/keys', methods=['POST'])
def post_project_keypairs(project_id):
    user_name = request.headers['user_name']

    args = request.form
    show_name = args['show_name']
    public_key = args['public_key']

    if not utils.is_project_admin(user_name, project_id, True):
        if not utils.is_active(project_id, user_name):
            return dict(status=403, msg='fail', data=u'您已被冻结!')
        if not utils.check_permission(user_name, project_id, 'vps', 'c'):
            return dict(status=403, msg='fail', data=u'您无权创建密钥对!')

    cluster_ids = utils.get_project_clusters_ids(project_id, True)
    print 'cluster_ids: %s' % cluster_ids

    if cluster_ids is None:
        resp = {
            'status': 1,
            'msg': 'fail',
            'data': 'No cluster attached on this project!',
        }
    else:
        key_id = '%s_%s' % (project_id, uuid.uuid4().hex[:4])
        for c_id in cluster_ids:
            print 'cluster: %s' % c_id
            s, m = utils.post_single_key(c_id, project_id, user_name,
                                         key_id, show_name, public_key)

        print '%s:%s' % (s, m)
        if s:
            resp = {
                'status': s,
                'msg': 'fail',
                'data': m,
            }
        else:
            resp = {
                'status': 0,
                'msg': 'success',
                'data': {
                    'key_id': key_id,
                    'show_name': show_name,
                    'fingerprint': m['fingerprint'],
                },
            }
    return resp
